package cn.edu.buaa.sei.jdat.vt.widgets.graph;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Random;

import cn.edu.buaa.sei.jdat.graph.DependencyGraph;
import cn.edu.buaa.sei.jdat.graph.JarDependency;
import cn.edu.buaa.sei.jdat.graph.PackageDependency;
import cn.edu.buaa.sei.jdat.model.Jar;
import cn.edu.buaa.sei.jdat.vt.res.Resources;
import cn.edu.buaa.sei.jdat.vt.widgets.ValueBubble;

import com.cocotingo.snail.MouseEvent;
import com.cocotingo.snail.Vector;
import com.cocotingo.snail.View;
import com.cocotingo.snail.dispatcher.Dispatcher;

public class GraphView extends View {
	
	private DependencyGraph graph;
	private final LineView lineView;
	private final Hashtable<String, JarBubble> jarBubbles;

	public GraphView(int left, int top, int width, int height) {
		super(left, top, width, height);
		setBackgroundColor(null);
		setPaintingEnabled(false);
		setClipped(true);
		jarBubbles = new Hashtable<String, JarBubble>();
		addSubview(lineView = new LineView(0, 0, 0, 0) {
			
			Line selectedLine = null;
			List<Line> old_mouse_on = new ArrayList<Line>();
			
			@Override
			protected void postMouseMoved(MouseEvent e) {
				List<Line> lines = getLinesOn(e.getPosition(this));
				if (lines.size() == 0) {
					// clear
					for (Line line : old_mouse_on) {
						if (line != selectedLine) line.setColor(Resources.COLOR_GRAPH_ARROW);
					}
				} else if (!lines.equals(old_mouse_on)) {
					// clear
					for (Line line : old_mouse_on) {
						if (line != selectedLine) line.setColor(Resources.COLOR_GRAPH_ARROW);
					}
					// highlight new
					for (Line line : lines) {
						if (line != selectedLine) line.setColor(Resources.COLOR_GRAPH_ARROW_HIGHLIGHTED);
					}
					e.handle();
				} else {
					e.handle();
				}
				old_mouse_on = lines;
			}
			
			@Override
			protected void postMousePressed(MouseEvent e) {
				List<Line> lines = getLinesOn(e.getPosition(this));
				if (lines.size() == 0) {
					// deselect
					deselectCurrent();
				} else if (lines.size() == 1) {
					deselectCurrent();
					selectLineAtIndex(lines, 0);
				} else if (lines.equals(old_mouse_on) && selectedLine != null) {
					// selection cycle
					int index = old_mouse_on.indexOf(selectedLine);
					if (index == -1) {
						deselectCurrent();
						selectLineAtIndex(lines, 0);
					} else {
						deselectCurrent();
						index = (index + 1) % old_mouse_on.size();
						selectLineAtIndex(old_mouse_on, index);
					}
				} else {
					deselectCurrent();
					selectLineAtIndex(lines, 0);
				}
			}
			
			private void deselectCurrent() {
				if (selectedLine != null) {
					selectedLine.setColor(old_mouse_on.contains(selectedLine) ? Resources.COLOR_GRAPH_ARROW_HIGHLIGHTED : Resources.COLOR_GRAPH_ARROW);
					((Arrow) selectedLine).deselected();
					selectedLine = null;
				}
			}
			
			private void selectLineAtIndex(List<Line> lines, int index) {
				Arrow line = (Arrow) lines.get(index);
				line.setColor(Resources.COLOR_GRAPH_ARROW_SELECTED);
				line.selected();
				selectedLine = line;
			}
			
		});
	}
	
	public DependencyGraph getGraph() {
		return graph;
	}
	
	public void setGraph(DependencyGraph graph, final Runnable finishBlock) {
		this.graph = graph;
		
		// clear current graph
		lineView.removeAllLines();
		for (JarBubble bubble : jarBubbles.values()) {
			bubble.removeFromSuperView();
		}
		jarBubbles.clear();
		
		// generate new graph
		if (graph != null) generateGraph();
		
		// TODO: layout dependency graph
		if (finishBlock != null) {
			Dispatcher.execute(new Runnable() {
				@Override
				public void run() {
					Dispatcher.executeAndSyncWithGUI(new Runnable() {
						@Override
						public void run() {
							Random r = new Random();
							for (JarBubble bubble : jarBubbles.values()) {
								bubble.setCenter(Math.abs(r.nextInt()) % getWidth(), Math.abs(r.nextInt()) % getHeight());
								Vector vector = bubble.getPosition();
								bubble.verifyDraggingPosition(vector);
								bubble.setPosition(vector);
							}
							finishBlock.run();
						}
					});
				}
			});
		}
		
	}
	
	private JarBubble createBubble(Jar jar) {
		JarBubble bubble = new JarBubble(jar) {
			@Override
			protected void verifyDraggingPosition(Vector v) {
				if (v.x < 0) v.x = 0;
				else if (v.x + getWidth() > GraphView.this.getWidth()) v.x = GraphView.this.getWidth() - getWidth();
				if (v.y < 0) v.y = 0;
				else if (v.y + getHeight() > GraphView.this.getHeight()) v.y = GraphView.this.getHeight() - getHeight();
			}
		};
		jarBubbles.put(jar.getURI(), bubble);
		return bubble;
	}
	
	private Arrow createArrow(JarBubble b1, JarBubble b2) {
		Arrow arrow = new Arrow(lineView, b1, b2) {
			private ValueBubble valueBubble = null;
			@Override
			protected void selected() {
				// get dependency
				Jar jar1 = getBubble1().getJar();
				Jar jar2 = getBubble2().getJar();
				JarDependency dep = graph.getDependency(jar1, jar2);
				
				if (dep != null) {
					dep.generateDetails();
					PackageDependency[] pkgDeps = dep.getDependencies();
					
					valueBubble = createValueBubble(pkgDeps);
					syncValueBubblePosition();
					GraphView.this.addSubview(valueBubble);
				}
			}
			@Override
			protected void deselected() {
				if (valueBubble != null) {
					valueBubble.removeFromSuperView();
					valueBubble = null;
				}
			}
			private void syncValueBubblePosition() {
				if (valueBubble != null) {
					double x = (x2 - x1) * 0.8;
					double y = (y2 - y1) * 0.8;
					valueBubble.setAnchor(x1 + (int) x, y1 + (int) y);
				}
			}
			@Override
			public void setEnd1(Vector v1) {
				super.setEnd1(v1);
				syncValueBubblePosition();
			}
			@Override
			public void setEnd2(Vector v2) {
				super.setEnd2(v2);
				syncValueBubblePosition();
			}
			@Override
			public void remove() {
				super.remove();
				deselected();
			}
		};
		b1.addArrowAsEnd1(arrow);
		b2.addArrowAsEnd2(arrow);
		return arrow;
	}
	
	private ValueBubble createValueBubble(final PackageDependency[] pkgDeps) {
		ValueBubble vb = new ValueBubble(0, 0) {
			{
				getTextView().setText(String.valueOf(pkgDeps.length));
			}
			@Override
			protected void mouseEntered() {
				setIndex(getSuperView().count() - 1);
			}
			@Override
			protected void postMousePressed(MouseEvent e) {
				e.handle();
				// TODO: show dependency details
			}
		};
		return vb;
	}
	
	private void generateGraph() {
		
		// add jar node
		Jar[] internalJars = graph.getInternalJars();
		for (Jar jar : internalJars) {
			JarBubble bubble = createBubble(jar);
			bubble.makeOrange();
			addSubview(bubble);
		}
		Jar[] externalJars = graph.getExternalJars();
		for (Jar jar : externalJars) {
			JarBubble bubble = createBubble(jar);
			bubble.makePink();
			addSubview(bubble);
		}
		Jar jar = graph.getUnknownJar();
		if (jar != null && graph.getUnknownPackages().length > 0) {
			JarBubble bubble = createBubble(jar);
			bubble.makeBrown();
			addSubview(bubble);
		}
		
		// add lines
		for (JarDependency jd : graph.getInternalDependencies()) {
			String u1 = jd.getSource().getURI();
			String u2 = jd.getDestination().getURI();
			if (jarBubbles.containsKey(u1) && jarBubbles.containsKey(u2)) {
				createArrow(jarBubbles.get(u1), jarBubbles.get(u2));
			}
		}
		for (JarDependency jd : graph.getExternalDependencies()) {
			String u1 = jd.getSource().getURI();
			String u2 = jd.getDestination().getURI();
			if (jarBubbles.containsKey(u1) && jarBubbles.containsKey(u2)) {
				createArrow(jarBubbles.get(u1), jarBubbles.get(u2));
			}
		}

	}
	
	@Override
	public View setSize(int width, int height) {
		super.setSize(width, height);
		if (lineView != null) {
			lineView.setSize(width, height);
		}
		return this;
	}

}
